﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NDBGen.Descriptors;
using NDBGen.Descriptors.Columns.DataTypes;

namespace NDBGen
{
    public static class Extensions
    {
        public static Int32 CountStartingTabs(this String s)
        {
            var parts = s.Split(Parser.TAB_STRINGS, StringSplitOptions.None);
            var tabs = 0;
            foreach (var part in parts)
            {
                if (part == String.Empty) tabs++;
                else break;
            }

            return tabs;
        }

        internal static List<String> GetLines(this String s)
        {
            if (s == null) return null;
            else return s.Split(new Char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Where(l => !l.Trim().StartsWith("-") && l.Trim() != String.Empty).ToList();
        }

        internal static String CombineLines(this IEnumerable<String> collection)
        {
            if (collection.Any()) return collection.Aggregate((current, next) => current + Environment.NewLine + next);
            else return String.Empty;
        }

        /// <summary>
        /// Chops a multiline string based on the indentation. See remarks for full information.
        /// </summary>
        /// <remarks>
        /// Based on the indentation of the first line of the string, the string will be broken 
        /// up into multiple sub-strings every time the indentatino matches that of the first line.
        /// For example, if you have:
        /// <code>
        /// twinkle
        ///     twinkle twinkle little star
        ///     how i wonder what you are
        /// row row
        ///     row row row your boat
        ///     gently down the stream
        /// spider
        ///     the itsy bitsy spider
        ///     climbed up the water spout
        /// </code>
        /// This string will be chopped into three parts, one for each nursury rhyme.
        /// </remarks>
        /// <param name="s">ParsedInput string.</param>
        /// <returns>A list of strings that represent the input string "chopped".</returns>
        internal static List<String> Chop(this String s)
        {
            var list = new List<String>();
            if (!String.IsNullOrEmpty(s))
            {
                var lines = s.GetLines();
                var indentation = lines.GetIndentationStats();
                var breaker = lines.First().CountStartingTabs();
                var breakpoints = indentation.Where(x => x.Value == breaker).Select(x => x.Key).ToList();
                for (Int32 n = 0; n < breakpoints.Count; n++)
                {
                    var start = breakpoints[n];
                    var end = (n == breakpoints.Count - 1) ? lines.Count : breakpoints[n + 1];
                    var section = lines.Skip(start).Take(end - start).ToList();
                    list.Add(section.CombineLines());
                }
            }
            return list;
        }

        internal static Dictionary<Int32, Int32> GetIndentationStats(this IEnumerable<String> collection)
        {
            var indentation = new Dictionary<Int32, Int32>();
            var index = 0;
            foreach (var item in collection)
            {
                indentation.Add(index, item.CountStartingTabs());
                index++;
            }
            return indentation;
        }

        public static String ToPascalCase(this String s)
        {
            var sb = new StringBuilder();
            Boolean nextCap = true;
            foreach (var ch in s.ToCharArray())
            {
                if (nextCap)
                {
                    sb.Append(ch.ToString().ToUpper());
                    nextCap = false;
                }
                else
                {
                    if (ch == '_' || ch == ' ')
                    {
                        nextCap = true;
                    }
                    else
                    {
                        sb.Append(ch);
                    }
                }
            }
            var result = sb.ToString();
            // (?![a-z]) means "not followed by a lowercase letter"
            result = Regex.Replace(result, @"Id(?![a-z])", "ID");
            result = Regex.Replace(result, @"Ip(?![a-z])", "IP");
            return result;
        }

        public static String Depluralize(this String s)
        {
            if (s == null) return null;
            if (s.EndsWith("tatuses")) return s.Replace("tatuses", "tatus");
            if (s.EndsWith("ies")) return s.Replace("ies", "y");
            if (s.EndsWith("xes")) return s.Substring(0, s.Length - 2);
            if (s.EndsWith("ches")) return s.Substring(0, s.Length - 2);
            if (s.EndsWith("esses")) return s.Substring(0, s.Length - 2);

            // this rule must be last or it'll produce false positives
            if (s.EndsWith("s")) return s.TrimEnd('s');
            return s;
        }

        public static IOrderedEnumerable<T> SortOrdered<T>(this IEnumerable<T> collection)
        {
            var order = collection.OrderBy(i => i as IOrderedMetadata == null)
                .ThenBy(i => { var ordered = i as IOrderedMetadata; return ordered == null ? 0 : ordered.GenerationIndex; })
                .ThenBy(i => i.GetType().FullName);

            return order;
        }

        internal static IColumnDataTypeDescriptor FindOneForDataType(this ExportLibrary<IColumnDataTypeDescriptor, ITokenDescriptorMetadata> library, DataType dataType)
        {
            return library.FindOne(d => (d.DataTypes & dataType) == dataType);
        }
    }
}